package L;

import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

/**
 * FETT LOGGEN ALDER MIT STACKTRACE UND ALLES
 * @version 23
 * @since 1850
 * @author tob800
 */
public class L implements LoggerListener {
    /////////////////////
    /**
     * 
     */
    public static int LOGLEVEL = -1;    /////////////////////////////////
    public static final int LOGLEVEL_SUPERDEBUG = -1;
    public static final int LOGLEVEL_DEBUG = 0;
    public static final int LOGLEVEL_NOTICE = 1;
    public static final int LOGLEVEL_WARNING = 2;
    public static final int LOGLEVEL_ERROR = 3;
    public static final int LOGLEVEL_FATAL_ERROR = 4;
    ////
    public static boolean showHashcode = false;
    public static boolean showCallingMethod = true;
    public static boolean showCallingObject = true;
    private boolean showTimestamp = true;
    private static int SPACING = 50;
    private static final String blankstring = "                                                                                                                        ";
    private static L INSTANCE;
    private List<LoggerListener> listeners = new ArrayList<LoggerListener>(2);
    private List<Stopwatch> stopWatches = new ArrayList<Stopwatch>();

    private L() {
        INSTANCE = this;
        listeners.add(this);
    }

    /**
     * static singleton getter for current Logger instance
     * @return the current, or if not present, a new Instance of the logger
     */
    public static L getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new L();
        }
        return INSTANCE;
    }

    public static Stopwatch start() {
        return start("default");
    }

    public static Stopwatch start(String name) {
        Stopwatch s = new Stopwatch(name).start();
        getInstance().stopWatches.add(s);
        return s;
    }

    public static Stopwatch stop() {
        return stop("default");
    }

    public static Stopwatch stop(String name) {
        return getStopwatch(name).stop();
    }

    public static void stopAndPrint() {
        stopAndPrint("default");
    }

    public static void stopAndPrint(String name) {
        out(stop(name).toString());
    }

    public static void stopAndPrintAndReset() {
        stopAndPrint("default");
        reset();
    }

    public static void stopAndPrintAndReset(String name) {
        stopAndPrint(name);
        reset(name);
    }

    private static void reset() {
        reset("default");
    }

    private static void reset(String name) {
        getStopwatch(name).reset();
    }

    public static Stopwatch getStopwatch() {
        return getStopwatch("default");
    }

    public static Stopwatch getStopwatch(String name) {
        List<Stopwatch> watches = getInstance().stopWatches;
        for (Stopwatch s : watches) {
            if (s.getName().equals(name)) {
                return s;
            }
        }
        return null;
    }

    /**
     * register a loglistener
     * @param l a LoggerListener implementing class
     */
    public static void addLogListener(LoggerListener l) {
        getInstance().listeners.add(l);
    }

    public static void removeLogListener(LoggerListener l) {
        getInstance().listeners.remove(l);
    }

    public static void mark() {
        m();
    }

    public static void mark(Object o) {
        m(o);
    }

    public static void m() {
        out("<<MARK>>" + getStackTraceInfos() + "<<MARK>>");
    }

    public static void m(Object o) {
        out("<<MARK>>" + getStackTraceInfos() + " Output:" + o.toString() + "<<MARK>>");
    }

    public static void s() {
        separator();
    }

    public static void separator() {
        separator(250);
    }

    public static void separator(int len) {
        char[] seps = new char[len];
        Arrays.fill(seps, '_');
        out(new String(seps));
    }

    public static void o(Object... objects) {
        StringBuffer out = new StringBuffer();
        if (objects.length > 1) {
            StringTokenizer t = new StringTokenizer(objects[0].toString(), ",");
            String[] labels = new String[t.countTokens() + 1];
            for (int i = 0; i < t.countTokens() + 1; i++) {
                labels[i] = t.nextToken();
            }
            for (int i = 1; i < objects.length; i++) {
                String separatorStringIfNotLastVariable = (i != (objects.length - 1) ? " | " : "");
                String objectToString = objects[i] != null ? objects[i].toString() : "null";
                String currentLabel;
                if (i - 1 < labels.length) {
                    currentLabel = labels[i - 1];
                } else {
                    currentLabel = "---";
                }
                out.append(currentLabel + ": " + objectToString + separatorStringIfNotLastVariable);
            }
        } else if (objects.length == 1) {
            out.append(objects[0]);
        } else {
            return;
        }
        out(out.toString());
    }

    /**
     * mega debug log 
     * @param message
     */
    public static void dd(Object message) {//debug
        if (LOGLEVEL < 0) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " ::: " + message.toString());
        }
    }

    /**
     * mega debug log
     * @param message 
     * @param previousCaller if set true, show previous caller too
     */
    public static void dd(Object message, boolean previousCaller) {//debug
        String otherStackTrace = previousCaller ? getStackTraceInfosByPosition(4) : "";
        if (LOGLEVEL < 0) {
            out(fixedLength(getStackTraceInfos(), SPACING) + ",previous caller:" + otherStackTrace + " ::: " + message.toString());
        }
    }

    /**
     * log something with debug status
     * @param message
     */
    public static void d(Object message) {//debug
        if (LOGLEVEL <= 0) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " ::: " + message.toString());
        }
    }

    /**
     * log something with debug status
     * @param message
     * @param b if true, shows previous caller
     */
    public static void d(Object message, boolean b) {//debug
        String otherStackTrace = b ? getStackTraceInfosByPosition(4) : "";
        if (LOGLEVEL <= 0) {
            out(fixedLength(getStackTraceInfos(), SPACING) + ",previous caller:" + otherStackTrace + " ::: " + message.toString());
        }
    }

    /**
     * log something with notice status
     * @param message
     */
    public static void n(Object message) {//notice
        if (LOGLEVEL <= 1) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " ::: " + message.toString());
        }
    }

    /**
     * log something with warning status
     * @param message
     */
    public static void w(Object message) {//warning
        if (LOGLEVEL <= 2) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " ::: WARNING: " + message.toString());
        }
    }

    /**
     * log something with error status
     * @param message
     * @param e exception to be shown
     */
    public static void e(Object message, Exception e) {//error
        if (LOGLEVEL <= 3) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " ::: ERROR: " + message.toString() + " Exception:" + e.toString());
        }
    }

    /**
     * log something with error status
     * @param message
     */
    public static void e(Object message) {//error
        if (LOGLEVEL <= 3) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " ::: ERROR: " + message.toString());
        }
    }

    /**
     * log something with error status
     * @param message
     * @param showpreviousCallerStack show previous caller too ?
     */
    public static void e(Object message, boolean showpreviousCallerStack) {//error
        String otherStackTrace = showpreviousCallerStack ? getStackTraceInfosByPosition(4) : "";
        if (LOGLEVEL <= 3) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " called by " + otherStackTrace + " ::: ERROR: " + message.toString());
        }
    }

    /**
     * log something with fatal error status
     * @param message
     */
    public static void f(Object message) {//fatal error
        if (LOGLEVEL <= 4) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " ::: FATAL ERROR: " + message.toString());
        }
    }

    /**
     * log something with fatal error status
     * @param message
     * @param e a error message to be displayed
     */
    public static void f(Object message, Exception e) {//fatal error
        if (LOGLEVEL <= 4) {
            out(fixedLength(getStackTraceInfos(), SPACING) + " ::: FATAL ERROR: " + message.toString() + " Exception:" + e.toString());
        }
    }

    private static void out(String s) {
        //timestamp ma hier rein, muss umgeschrieben werden
        String timestamp = "";
        if (getInstance().showTimestamp) {
            timestamp = System.currentTimeMillis() + "";
        }
        System.out.println(timestamp + ": " + s);
        if (INSTANCE != null) {
            for (LoggerListener l : INSTANCE.listeners) {
                l.receiveLog(s);
            }
        }
    }

    private static String fixedLength(String s, int length) {
        int n = length - s.length();
        if (n > 0) {
            return s + whitespace(n);
        } else {
            return s;
        }
    }

    private static String whitespace(int n) {
        return blankstring.substring(0, n);
    }

    public static String getStackTraceInfosByPosition(int StackTracePosition) {
        StackTraceElement ste = new Exception().getStackTrace()[StackTracePosition];
        return stringAb(ste.getClassName(), "$") + "." + stringAb(ste.getMethodName(), ".");
    }

    private static String getStackTraceInfos() {
        return getStackTraceInfosByPosition(3);
    }

    private static String stringAb(String text, String mark) {
        int i = text.indexOf(mark);
        if (i > 0 && i < text.length()) {
            return text.substring(i + 1);
        } else {
            return text;
        }
    }

    public int getLOGLEVEL() {
        return LOGLEVEL;
    }

    public void setLOGLEVEL(int LOGLEVEL) {
        L.LOGLEVEL = LOGLEVEL;
    }

    public int getSPACING() {
        return SPACING;
    }

    public void setSPACING(int SPACING) {
        L.SPACING = SPACING;
    }

    public boolean isShowingCallingMethods() {
        return showCallingMethod;
    }

    public void setShowCallingMethod(boolean showCallingMethod) {
        L.showCallingMethod = showCallingMethod;
    }

    public boolean isShowingCallingObjects() {
        return showCallingObject;
    }

    public void setShowCallingObject(boolean showCallingObject) {
        L.showCallingObject = showCallingObject;
    }

    public boolean isShowingHashcode() {
        return showHashcode;
    }

    public void setShowHashcode(boolean showHashcode) {
        L.showHashcode = showHashcode;
    }

    public static void setShowTimestamp(boolean showTimestamp) {
        getInstance().showTimestamp = showTimestamp;
    }

    /////////////////// VERWALTUNG LOGMANAGER
    private List<String> logs = new ArrayList<String>(500);

    public void receiveLog(String s) {
        logs.add(s);
    }

    public static void writeLogsToStream(OutputStream os) {
        try {
            for (String l : getInstance().logs) {
                os.write((l + "\n").getBytes());
            }
        } catch (IOException iOException) {
            iOException.printStackTrace();
        }
    }

    public static void saveToFile() {
        try {
            FileWriter fw = new FileWriter("log800.txt");
            for (String s : getInstance().logs) {
                fw.write(s + "\n");
            }
        } catch (IOException ex) {
            Logger.getLogger(LogManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static List<String> getLogs() {
        return getInstance().logs;
    }

}
